#pragma once
#include <stdint.h>

template<class T> inline GLenum GetGLType() { return 0; }
template<> inline GLenum GetGLType<const int8_t>() { return GL_BYTE; }
template<> inline GLenum GetGLType<const uint8_t>() { return GL_UNSIGNED_BYTE; }
template<> inline GLenum GetGLType<const int16_t>() { return GL_SHORT; }
template<> inline GLenum GetGLType<const uint16_t>() { return GL_UNSIGNED_SHORT; }
template<> inline GLenum GetGLType<const int32_t>() { return GL_INT; }
template<> inline GLenum GetGLType<const uint32_t>() { return GL_UNSIGNED_INT; }
template<> inline GLenum GetGLType<const float>() { return GL_FLOAT; }
template<> inline GLenum GetGLType<const double>() { return GL_DOUBLE; }

struct VBOAttribute {
	GLuint index;
	GLint size;
	GLenum type;
	GLboolean normalize;
	GLvoid* offset;

	bool enabled;

	VBOAttribute(GLuint _index, GLint _size, GLenum _type, GLboolean _normalize = 0) : index(_index), size(_size), type(_type), normalize(_normalize), enabled(true) {};
};


class VBOAttributes {
public:
	const VBOAttribute& getAttribute(unsigned int i) const;
	VBOAttribute& getAttribute(unsigned int i);
	unsigned int getNumAttributes() const;
	unsigned int getStride() const;

	void setAttributes(VBOAttribute* attributes, unsigned int N);
	template<unsigned int N> void setAttributes(VBOAttribute(&attributes)[N]) { setAttributes(attributes, N); }
private:
	VBOAttribute* m_attributes;
	unsigned int m_num_attributes;
	unsigned int m_stride;
};

class ElementAttributes {
public:
	ElementAttributes() : m_num_indices(0) {}
	ElementAttributes(unsigned int num_indices, GLenum index_type) : m_num_indices(num_indices), m_index_type(index_type) {}
	unsigned int getNumIndices() const { return m_num_indices; }
	GLenum getIndexType() const { return m_index_type;  }
	void setNumIndices(unsigned int num_indices) { m_num_indices = num_indices;  }
	void setIndexType(GLenum index_type) { m_index_type = index_type; }
private:
	unsigned int m_num_indices;
	GLenum m_index_type;
};

class BufferObject {
	GLenum m_target;
	GLenum m_usage;

	GLuint m_buffer;

public:
	BufferObject(GLenum target, GLenum usage = GL_STATIC_DRAW);

	~BufferObject();

	void create();

	void bind() const;

	void bufferData(GLuint length, const void* ptr);

	int getSize();

	template<typename T, GLuint N> void bufferData(T(&data)[N]) { bufferData(sizeof(T)* N, data); }

	operator GLuint() { return m_buffer; }
};

class ElementBuffer : public BufferObject, public ElementAttributes {
public:
	ElementBuffer(GLenum usage = GL_STATIC_DRAW);

	template<typename T, unsigned int N> 
	void bufferIndices(T(&data)[N]);
};

template<typename T, unsigned int N> void 
ElementBuffer::bufferIndices(T(&data)[N]) {
	bufferData(data);
	setNumIndices(N);
	setIndexType(GetGLType<const T>());
}

template<typename T, unsigned int N> void bufferIndices(unsigned short(&data)[N]) {
	bufferData(data);
	setNumIndices(N);
	setIndexType(GetGLType<const T>());
}



class VertexBuffer : public BufferObject, public VBOAttributes {
public:
	VertexBuffer() : BufferObject(GL_ARRAY_BUFFER) {}

private:
	GLenum m_mode;
};



void RenderBuffer(GLenum mode, const BufferObject& buffer, const VBOAttributes& attributes, const BufferObject& elements, const ElementAttributes element_attributes);
inline void RenderBuffer(GLenum mode, const VertexBuffer& buffer, const ElementBuffer& elements) { RenderBuffer(mode, buffer, buffer, elements, elements); }